﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;
using SimpleServiceBus.Bus.Pipeline;
using SimpleServiceBus.Endpoint;
using SimpleServiceBus.Utilities;

namespace SimpleServiceBus.Bus.MessageManagement
{
    public class DefaultMessageTypeManagementService : EndpointServiceBase, IMessageTypeManagementService
    {
        private readonly ReaderWriterLock _rwLock = new ReaderWriterLock();
        
        public DefaultMessageTypeManagementService()
        {
            MessageTypeMap = new Dictionary<Type, string>();
            HeaderValueProviders = new List<HeaderValueProviderBinding>();
        }

        protected Dictionary<Type, string> MessageTypeMap { get; private set; }
        protected List<HeaderValueProviderBinding> HeaderValueProviders { get; private set; }

        #region IMessageTypeManagementService Members

        public void RegisterMessageType(Type messageType)
        {
            RegisterMessageType(messageType, null);
        }

        public void RegisterMessageType(Type messageType, string destinationAddress)
        {
            RegisterMessageType(messageType, destinationAddress, false);
        }

        public void RegisterMessageAssembly(Assembly asm)
        {
            RegisterMessageAssembly(asm, null);
        }

        public void RegisterMessageAssembly(Assembly asm, string destinationAddress)
        {
            AssertNotStarted("RegisterMessageAssembly");
            foreach (Type type in asm.GetTypes())
            {
                //We won't overwrite when adding bulk assemblies, because individual
                //TMessageType registrations always take precedence.
                if (type.CanSerialize())
                    RegisterMessageType(type, destinationAddress, true);
            }
        }

        public string GetDestinationForMessageType(Type messageType)
        {
            if (messageType == null) return null;

            string destination;
            MessageTypeMap.TryGetValue(messageType, out destination);
            return destination;
        }

        public string GetDestinationFor<MessageType>()
        {
            return GetDestinationForMessageType(typeof (MessageType));
        }

        public string GetDestinationForMessage(object message)
        {
            if (message == null) return null;

            return GetDestinationForMessageType(message.GetType());
        }

        public Type[] GetRegisteredMessageTypes()
        {
            return MessageTypeMap.Keys.ToArray();
        }

        public void RegisterMessageHeaderFor<TMessageType>(string headerKey, IMessageValueProvider headerValueProvider)
        {
           using (new WriterLock(_rwLock,TimeSpan.FromSeconds(10)))
           {
               HeaderValueProviders.Add(new HeaderValueProviderBinding
                                            {
                                                HeaderKey = headerKey,
                                                HeaderValueProvider = headerValueProvider,
                                                MessageType = typeof (TMessageType)
                                            });
           }
        }

        public void GenerateMessageHeaders(MessageEnvelope message,IMessageContext messageContext)
        {
            using (new ReaderLock(_rwLock,TimeSpan.FromSeconds(30)))
            {
                foreach (HeaderValueProviderBinding binding in HeaderValueProviders)
                {
                    //If the binding matches the message, and the key doesn't already exist 
                    //(we won't overwrite existing values) then we'll fetch a value
                    //from the message to stick in the appropriate header
                    if (binding.Matches(message) && message.Headers[binding.HeaderKey] == null)
                        message.Headers[binding.HeaderKey] = binding.GetHeaderValue(message, messageContext);
                }
            }
        }

        #endregion

        protected void RegisterMessageType(Type messageType, string destinationAddress, bool doNotOverwrite)
        {
            AssertNotStarted("RegisterMessageType");
            if (messageType == null) throw new ArgumentNullException("messageType");

            using (new WriterLock(_rwLock, TimeSpan.FromSeconds(10)))
            {
                string found;
                MessageTypeMap.TryGetValue(messageType, out found);

                //If no value was found, or overwriting is allowed and a destination was provided,
                //then we'll add the entry. Otherwise, no action is necessary.
                if (found == null ||
                    (String.IsNullOrEmpty(destinationAddress) == false && doNotOverwrite == false))
                {
                    MessageTypeMap[messageType] = destinationAddress;
                }
            }
        }

        protected override void ShutDownService()
        {
        }

        protected override void StartService()
        {
        }

        protected void AssertNotStarted(string callingMethod)
        {
            if (IsRunning)
                throw new InvalidOperationException(
                    callingMethod +
                    " cannot be invoked once the endpoint has been started. Please register all appropriate types prior to calling Start() on the endpoint.");
        }
    }
}